package com.jly.select;

import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 虚拟机迁移的选择算法程序实现
 *
 * @author lanyangji
 */
public class SelectMain {

    /**
     * 虚拟机集合
     */
    private static List<VMNode> VM_NODES = DataDemo1.VM_NODES;
    /**
     * 全图的“加权”关联矩阵
     * 1. 矩阵每行每列上的值，表示的是对应两个虚拟机节点的连接关系
     * 1） 如果值为0，表示两个对应位置上的虚拟机节点没有连接关系（不通信）
     * 2） 如果值大于0，表示两个对应位置上的虚拟机节点有连接关系（通信）
     * 并且这个值对应的是两个虚拟机节点的通信量
     */
    private static final Integer[][] WEIGHTED_RELEVANT_MATRIX_OF_FULL_GRAPH = DataDemo1.WEIGHTED_RELEVANT_MATRIX_OF_FULL_GRAPH;

    /**
     * 物理机最低负载假设值
     */
    private static final Integer LOWEST_WEIGHT = 8;

    /**
     * 物理机最高负载假设值
     * 如此一来，当物理机的负载在 8(lowest_weight) - 12(highest_weight) 之间，表示合理负载
     * 低于8表示低负载，高于12表示负载过大
     */
    private static final Integer HIGHEST_WEIGHT = 12;

    /**
     * 存储用户，和他的虚拟机节点集合
     */
    private static Map<String, List<VMNode>> user_and_vms;

    /**
     * 主方法
     *
     * @param args
     */
    public static void main(String[] args) {
        // 打印边界信息
        printMethodBoundary("*****************************初始化数据**************************************");


        // 组装用户，及其对应的虚拟机
        user_and_vms = VM_NODES.stream().collect(Collectors.groupingBy(VMNode::getOwner));

        System.out.println("计算通信成本之前，用户与其虚拟机集合为：");
        // 打印用户和他的虚拟机集合
        printUserAndHisVMList();
        System.out.println("\n\n");

        // 打印全图的“加权”关联矩阵
        System.out.println("打印全图的'加权'关联矩阵：");
        for (Integer[] integers : WEIGHTED_RELEVANT_MATRIX_OF_FULL_GRAPH) {
            for (Integer integer : integers) {
                System.out.printf("\t %d \t", integer);
            }
            System.out.println();
            System.out.println("\t ------------------------------------------------------------------");
        }
        System.out.println("\n\n");


        // 打印边界信息
        printMethodBoundary("*****************************开始分析***************************************");
        // 提取所有的无向连图子图
        for (int i = 0; i < WEIGHTED_RELEVANT_MATRIX_OF_FULL_GRAPH.length; i++) {
            // 获取矩阵第 i 行的非0个数
            long count = Stream.of(WEIGHTED_RELEVANT_MATRIX_OF_FULL_GRAPH[i]).filter(n -> n > 0).count();
            // 打印矩阵每一行的非0个数
            System.out.printf("打印矩阵第【%d】行的非0个数：\t【%d】\n", i, count);

            // 如果非0个数为0
            if (count == 0) {
                // 则该节点为孤立节点
                System.out.printf("\t【%d】号虚拟机所在的行全为0，为孤立虚拟机节点，它不与其他任何虚拟机产生通信，通信量为【0】", i);
                // 得到该虚拟机节点
                VMNode vmNode = VM_NODES.get(i);
                // 加入通信量
                vmNode.setCommunicationCost(0);
                // 装载通信成本
                setCommunicationCost(vmNode);


                // 加入用户及用户的虚拟机集合的二元组集合
                user_and_vms.put(vmNode.getOwner(), Arrays.asList(vmNode));
            }

            // 如果非0个数大于1
            if (count > 0) {
                System.out.printf("\t【%d】号虚拟机所在的行非0个数为【%d】，为通信虚拟机，", i, count);
                Integer communicationCost = 0;
                for (int j = 0; j < WEIGHTED_RELEVANT_MATRIX_OF_FULL_GRAPH[i].length; j++) {
                    // 查找不为0的列，以计算通信量
                    if (WEIGHTED_RELEVANT_MATRIX_OF_FULL_GRAPH[i][j] > 0) {
                        communicationCost += WEIGHTED_RELEVANT_MATRIX_OF_FULL_GRAPH[i][j];
                    }
                }
                System.out.printf("它和与它相互通信的虚拟机产生的通信成本为【%d】", communicationCost);

                // 得到该虚拟机节点
                VMNode vmNode = VM_NODES.get(i);
                // 加入通信量
                vmNode.setCommunicationCost(communicationCost);
                setCommunicationCost(vmNode);

            }

            System.out.println("\n");
        }

        System.out.println("\n\n");

        System.out.println("计算通信成本之后，用户与其虚拟机集合为：");
        // 打印用户和他的用户虚拟机集合
        printUserAndHisVMList();
        System.out.println("\n\n");

        // 这是有价值的核心代码
        // 给 vm_nodes 按照通信成本升序排序，再按照重量降序排序
        List<VMNode> collect = VM_NODES.stream()
                .sorted(Comparator.comparing(VMNode::getCommunicationCost))
                .sorted((v1, v2) -> {
                    // 如果通信成本相同，则按照重量降序排序
                    if (v1.getCommunicationCost().equals(v2.getCommunicationCost())) {
                        return -(v1.getWeight().compareTo(v2.getWeight()));
                    }

                    return 0;
                })
                .collect(Collectors.toList());

        System.out.println("按照以【通信量非降序，重量降序】排序后的虚拟机序列为：");
        collect.forEach(n -> System.out.printf("\t 虚拟机编号为【%d】,拥有者为【%s】,重量为【%d】,通信成本为【%d】\n", n.getVmId(), n.getOwner(), n.getWeight(), n.getCommunicationCost()));

        System.out.println("\n\n");
        // 计算总负载量
        Integer sumWeight = collect.stream().collect(Collectors.summingInt(VMNode::getWeight));
        System.out.printf("虚拟机负载量之和为【%d】\n", sumWeight);

        // 用linkedList包装下拍好虚拟机队列
        //LinkedList<VMNode> vmNodes = new LinkedList<>(collect);
        // 如果大于物理机安全阈值上限，则触发迁移算法
        if (sumWeight > HIGHEST_WEIGHT) {
            System.out.printf("\t 虚拟机负载之和大于物理机安全阈值上限【%d】，触发迁移算法：\n", HIGHEST_WEIGHT);
            for (VMNode vmNode : collect) {
                // 如果当前虚拟机没有被迁移
                if (vmNode.getIsRemove() == 0) {
                    // 先判断，如果低于高负载，循环结束
                    if (sumWeight <= HIGHEST_WEIGHT) {
                        break;
                    }

                    // 如果去掉当前虚拟机节点之后，总负载低于 低负载水平之下，保留该虚拟机节点，进入下一次循环
                    if (sumWeight - vmNode.getWeight() < LOWEST_WEIGHT) {
                        continue;
                    }

                    // 如果去掉当前虚拟机节点，虚拟机仍然超重，则直接去掉
                    System.out.printf("\t\t 迁移当前虚拟机，编号为【%d】, 所属用户为【%s】, 重量为【%d】, 通信量为【%d】\n", vmNode.getVmId(), vmNode.getOwner(), vmNode.getWeight(), vmNode.getCommunicationCost());
                    // 总重量相应减少
                    sumWeight -= vmNode.getWeight();
                    System.out.printf("\t\t\t\t 迁移当前虚拟机之后，剩下虚拟机总重量为【%d】 \n", sumWeight);
                    // 迁移当前虚拟机节点
                    vmNode.setIsRemove(1);
                }
            }

        }

        System.out.println("\n\n");

        // 迁移完之后剩下的虚拟机
        System.out.println("迁移完之后剩下的虚拟机序列");
        collect.stream().forEach(
                vmNode -> {
                    if (vmNode.getIsRemove() == 0) {
                        System.out.printf("\t\t 虚拟机编号为【%d】，所属用户为【%s】，重量为【%d】，通信量为【%d】\n", vmNode.getVmId(), vmNode.getOwner(), vmNode.getWeight(), vmNode.getCommunicationCost());
                    }
                }
        );

    }

    private static void printMethodBoundary(String s) {
        System.out.println("****************************************************************************");
        System.out.println(s);
        System.out.println("****************************************************************************");
    }

    private static void printUserAndHisVMList() {
        for (Map.Entry<String, List<VMNode>> entry : user_and_vms.entrySet()) {
            System.out.printf("\t 【%s】的虚拟机集合为 \n", entry.getKey());
            for (VMNode vmNode : entry.getValue()) {
                System.out.printf("\t\t 虚拟机编号为【%d】，虚拟机重量为【%d】, 通信成本为【%d】\n", vmNode.getVmId(), vmNode.getWeight(), vmNode.getCommunicationCost());
            }
            System.out.println();
        }
    }

    /**
     * 给<用户，用户所拥有的虚拟机集合>二元组装载通信成本
     *
     * @param vmNode
     */
    private static void setCommunicationCost(VMNode vmNode) {
        user_and_vms.get(vmNode.getOwner()).stream().forEach(
                vm -> {
                    if (vm.getVmId().equals(vmNode.getVmId())) {
                        try {
                            BeanUtils.copyProperties(vmNode, vm);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                }
        );
    }

}
